<!DOCTYPE html>
<html lang="en">
  <head>
    <title>p2.js Platformer example</title>
    <meta charset="utf-8">
    <script src="../../build/p2.js"></script>
    <script src="js/p2.KinematicCharacterController.js"></script>
  </head>
  <body>
    <canvas width="600" height="400" id="myCanvas"></canvas>
    <p>Use arrow keys to control character</p>
    <code id="debug"></code>

    <script>
      var canvas;
      var ctx;
      var w, h;
      var cameraPos = [0, 0];
      var zoom = 50;
      var fixedDeltaTime = 1 / 60;
      var maxSubSteps = 10;
      var world;
      var characterBody;
      var rayDebugData = [];
      var player;

      // Collision groups
      var SCENERY_GROUP = 0x01;
      var PLAYER_GROUP = 0x02;

      init();
      requestAnimationFrame(animate);

      function init(){

        // Init canvas
        canvas = document.getElementById("myCanvas");
        w = canvas.width;
        h = canvas.height;
        ctx = canvas.getContext("2d");
        ctx.lineWidth = 1 / zoom;

        // Init world
        world = new p2.World();

        // Add some scenery
        addStaticBox(-3, 3, 0, 3, 1);
        addStaticBox(0, -1, 0, 7, 1);
        addStaticBox(-6, 0, Math.PI / 4, 1, 7);
        addStaticBox(4, 2, 0, 1, 6);
        addStaticCircle(-9, 1, 1, 2);

        // Add a character body
        var characterShape = new p2.Box({
          width: 1,
          height: 1.5,
          collisionGroup: PLAYER_GROUP
        });
        characterBody = new p2.Body({
          mass: 0,
          position:[0,3],
          fixedRotation: true,
          damping: 0,
          type: p2.Body.KINEMATIC
        });
        characterBody.addShape(characterShape);
        world.addBody(characterBody);

        // Create the character controller
        player = new p2.KinematicCharacterController({
          world: world,
          body: characterBody,
          collisionMask: SCENERY_GROUP,
          velocityXSmoothing: 0.0001,
          timeToJumpApex: 0.4,
          skinWidth: 0.1
        });

        // Update the character controller after each physics tick.
        world.on('postStep', function(){
          rayDebugData.length = 0;
          player.update(world.lastTimeStep);
        });

        // Store ray debug data
        player.on('raycast', function(evt){
          rayDebugData.push(
            evt.ray.from[0],
            evt.ray.from[1],
            evt.ray.to[0],
            evt.ray.to[1]
          );
        });

        // Set up key listeners
        var left = 0, right = 0;
        window.addEventListener('keydown', function(evt){
          switch(evt.keyCode){
            case 38: // up key
            case 32: player.setJumpKeyState(true); break; // space key
            case 39: right = 1; break; // right key
            case 37: left = 1; break; // left key
          }
          player.input[0] = right - left;
        });
        window.addEventListener('keyup', function(evt){
          switch(evt.keyCode){
            case 38: // up
            case 32: player.setJumpKeyState(false); break;
            case 39: right = 0; break;
            case 37: left = 0; break;
          }
          player.input[0] = right - left;
        });
      }

      function addStaticCircle(x, y, angle, radius){
        var shape = new p2.Circle({
          collisionGroup: SCENERY_GROUP,
          radius: radius
        });
        var body = new p2.Body({
          position: [x, y],
          angle: angle
        });
        body.addShape(shape);
        world.addBody(body);
      }

      function addStaticBox(x, y, angle, width, height){
        var shape = new p2.Box({
          collisionGroup: SCENERY_GROUP,
          width: width,
          height: height
        });
        var body = new p2.Body({
          position: [x, y],
          angle: angle
        });
        body.addShape(shape);
        world.addBody(body);
      }

      function drawBody(body){
        var x = body.interpolatedPosition[0],
            y = body.interpolatedPosition[1],
            s = body.shapes[0];
        ctx.save();
        ctx.translate(x, y);     // Translate to the center of the box
        ctx.rotate(body.interpolatedAngle);  // Rotate to the box body frame

        if(s instanceof p2.Box){
          ctx.fillRect(-s.width/2, -s.height/2, s.width, s.height);
        } else if(s instanceof p2.Circle){
          ctx.beginPath();
          ctx.arc(0, 0, s.radius, 0, 2 * Math.PI);
          ctx.fill();
          ctx.closePath();
        }

        ctx.restore();
      }

      function drawRay(startX, startY, endX, endY){
        ctx.beginPath();
        ctx.moveTo(startX, startY);
        ctx.lineTo(endX, endY);
        ctx.stroke();
        ctx.closePath();
      }

      function render(){
        ctx.fillStyle='black';
        ctx.fillRect(0,0,w,h);

        // Transform the canvas
        // Note that we need to flip the y axis since Canvas pixel coordinates
        // goes from top to bottom, while physics does the opposite.
        ctx.save();
        ctx.translate(w/2, h/2);  // Translate to the center
        ctx.scale(zoom, -zoom);   // Zoom in and flip y axis

        p2.vec2.lerp(
          cameraPos,
          cameraPos,
          [-characterBody.interpolatedPosition[0], -characterBody.interpolatedPosition[1]],
          0.05
        );
        ctx.translate(
          cameraPos[0],
          cameraPos[1]
        );

        // Draw all bodies
        ctx.strokeStyle='none';
        ctx.fillStyle='white';
        for(var i=0; i<world.bodies.length; i++){
          var body = world.bodies[i];
          drawBody(body);
        }

        ctx.strokeStyle='red';
        for(var i=0; i<rayDebugData.length; i+=4){
          drawRay(
            rayDebugData[i+0], rayDebugData[i+1],
            rayDebugData[i+2], rayDebugData[i+3]
          );
        }

        // Restore transform
        ctx.restore();
      }

      var lastTime;

      // Animation loop
      function animate(time){
        requestAnimationFrame(animate);

        // Compute elapsed time since last frame
        var deltaTime = lastTime ? (time - lastTime) / 1000 : 0;
        deltaTime = Math.min(1 / 10, deltaTime);

        // Move physics bodies forward in time
        world.step(fixedDeltaTime, deltaTime, maxSubSteps);

        // Render scene
        render();

        updateDebugLog();

        lastTime = time;
      }

      function updateDebugLog(){
        debug.innerHTML = [
          'player.collisions.above: ' + player.collisions.above,
          'player.collisions.below: ' + player.collisions.below,
          'player.collisions.left: ' + player.collisions.left,
          'player.collisions.right: ' + player.collisions.right,
          'player.collisions.climbingSlope: ' + player.collisions.climbingSlope,
          'player.collisions.descendingSlope: ' + player.collisions.descendingSlope,
          'player.collisions.slopeAngle: ' + player.collisions.slopeAngle,
          'player.collisions.slopeAngleOld: ' + player.collisions.slopeAngleOld,
          'player.collisions.faceDir: ' + player.collisions.faceDir,
          'player.collisions.fallingThroughPlatform: ' + player.collisions.fallingThroughPlatform
        ].join('<br>');
      }

    </script>
  </body>
</html>
